#include <cstdio>

#include <pthread.h>

#include "cameradaemon.h"
#include "timestamp.h"


#define DEBUG 0
using namespace cv;
void* captureThread(void* _cam){
    CameraDaemon* cam = (CameraDaemon*) _cam;
    cam->capture();
    return (void*) 0;
}
CameraDaemon::CameraDaemon()
{
    sem_init(&this->m_buffer_mutex, 0 ,1);
    sem_init(&this->m_prop_mutex, 0, 1);
    sem_init(&this->m_record_mutex, 0, 1);
    this->m_working = false;
    this->m_from_file = false;
    this->m_interval = 0;
    this->m_width = -1;
    this->m_height = -1;
    this->m_brightness = -1;
    this->m_contrast = -1;
    this->m_saturation = -1;
    this->m_hue = -1;
    this->m_need_record = false;
    this->m_recording = false;
}

CameraDaemon::~CameraDaemon(){
    if(this->m_capture.isOpened()){
        this->m_working = false;
        pthread_cancel(this->m_capture_thread);
        this->m_capture.release();
    }
    if(this->m_recording){
        this->stopRecord();
    }
    printf("camera closed\t");
    TimeStamp::now()->print();
}

void CameraDaemon::checkProp(){
    memset(&this->m_prop[0], false, sizeof(this->m_prop));
    int prop[] = {CAMERA_WIDTH_ID,
                  CAMERA_HEIGHT_ID,
                  CAMERA_BRIGHTNESS_ID,
                  CAMERA_CONTRAST_ID,
                  CAMERA_SATURATION_ID,
                  CAMERA_HUE_ID};
//    int prop[40];
//    for(int i = 0; i< 40 ;i++){
//        prop[i] = i;
//    }
    if(!this->m_capture.isOpened())
        return;
    for (int i = 0; i < sizeof(prop) / sizeof(int); i++){
        double p;
        bool can_set;
        if(((p = this->m_capture.get(prop[i]) )!= -1)){
            if(p == 0){
                p = 1;
                this->m_capture.set(prop[i], p);
                if(p == this->m_capture.get(prop[i])){
                    this->m_prop[prop[i]] = true;
                    this->m_capture.set(prop[i], 0);
                }
            }else{
                this->m_prop[prop[i]] = true;
            }
        }
#if DEBUG
        printf("%d p =%f can set = %s\n", prop[i], p, can_set?"true":"false");
#endif
    }

#if DEBUG
    for (int i = 0; i < CAMERA_PROP_COUNT; i++){
            if(this->m_prop[i]){
                printf("property %d is ok!\n", i);
            }
            else{
                printf("property %d is not ok!\n", i);
            }
    }
#endif
}


bool CameraDaemon::getImage(Mat &img, TimeStamp& t){
    sem_wait(&this->m_buffer_mutex);
    if(!this->m_buffer.data){
        sem_post(&this->m_buffer_mutex);
        return false;
    }else{
        this->m_buffer.copyTo(img);
        t = this->m_buffer_time;
        sem_post(&this->m_buffer_mutex);
        return true;
    }
}

bool CameraDaemon::startCapture(int device, int fps){
    this->m_device = device;
    this->m_fps = fps;
    this->m_interval = 1000 / this->m_fps;
    this->m_working = true;
    this->m_from_file = false;
    return (pthread_create(&this->m_capture_thread, NULL, &captureThread, (void*)this) == 0);
}

bool CameraDaemon::startCapture(std::string file, int fps){
    this->m_file = file;
    this->m_fps = fps;
    this->m_interval = 1000 / this->m_fps;
    this->m_working = true;
    this->m_from_file = true;
    return (pthread_create(&this->m_capture_thread, NULL, &captureThread, (void*)this) == 0);
}

void CameraDaemon::stopCapture(){
    pthread_cancel(this->m_capture_thread);
    if(this->m_capture.isOpened())
        this->m_capture.release();
}


void CameraDaemon::capture(){
    TimeStamp ts;
    if(!this->m_from_file){
        this->m_capture.open(this->m_device);
    }else{
        this->m_capture.open(this->m_file);
    }
    this->checkProp();
    this->m_width  = (int)this->getProp(CAMERA_WIDTH_ID);
    this->m_height = (int)this->getProp(CAMERA_HEIGHT_ID);
    this->m_brightness = this->getProp(CAMERA_BRIGHTNESS_ID);
    this->m_contrast = this->getProp(CAMERA_CONTRAST_ID);
    this->m_hue = this->getProp(CAMERA_HUE_ID);
    this->m_saturation = this->getProp(CAMERA_SATURATION_ID);
    while(this->m_working){
        Mat img;
        sem_wait(&this->m_prop_mutex);
        this->m_capture>>img;
        sem_post(&this->m_prop_mutex);
        ts.setNow();
        if(img.empty()){
            if(this->m_from_file) {
                this->m_capture.open(this->m_file);
            }else{
                break;
            }
        }
        sem_wait(&this->m_buffer_mutex);
        img.copyTo(this->m_buffer);
        this->m_buffer_time.setNow();
        sem_post(&this->m_buffer_mutex);
        if(this->m_need_record){
            this->m_need_record = false;
            this->startRecord();
        }
        sem_wait(&this->m_record_mutex);
        if(this->m_recording){
            this->m_record_writer.write(img);
        }
        sem_post(&this->m_record_mutex);
        img.release();

    }
    if(this->m_working){
        fprintf(stderr, "Error occurred when capturing!\n");
    }

}

void CameraDaemon::startRecord(){
    sem_wait(&this->m_record_mutex);
    if(!this->m_capture.isOpened()){
        m_need_record = true;
        return;
    }
    string fn = string();
//    fn.append(RECORD_FOLDER);
    fn.append(TimeStamp::now()->toFileName());
    fn.append(".avi");
    this->m_record_file = fn;
    this->m_record_writer = VideoWriter(this->m_record_file,
                                        CV_FOURCC('X','V','I','D'),
                                        this->m_fps,
                                        Size(this->m_width, this->m_height),
                                        true);
    this->m_recording = true;
    sem_post(&this->m_record_mutex);
}

void CameraDaemon::stopRecord(){
    sem_wait(&this->m_record_mutex);
    this->m_recording = false;
    this->m_record_writer = VideoWriter();
    sem_post(&this->m_record_mutex);
}

bool CameraDaemon::getRecordState(){
    return this->m_recording;
}

bool CameraDaemon::setProp(int id, double v){
    if(id < 0 || id >= CAMERA_PROP_COUNT || !this->m_prop[id]){
        return false;
    }else{
        sem_wait(&this->m_prop_mutex);
        this->m_capture.set(id, v);
        sem_post(&this->m_prop_mutex);
        return true;
    }
}

double CameraDaemon::getProp(int id){
     if(id < 0 || id >= CAMERA_PROP_COUNT || !this->m_prop[id]){
         return -1;
     }else{
         return this->m_capture.get(id);
     }
}
